/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.geode.distributed.internal.membership;

import java.io.NotSerializableException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.geode.SystemFailure;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.internal.DMStats;
import org.apache.geode.distributed.internal.DistributionMessage;
import org.apache.geode.internal.logging.InternalLogWriter;

/**
 * A MembershipManager is responsible for reporting a MemberView, as well as
 * having explicit protocol for leaving or joining the distributed system.<p>
 *
 * Note that it is imperative to send a new manager a postConnect message
 * after instantiation.
 * 
 * 
 */
public interface MembershipManager {

  /** this must be sent to the manager after instantiation to allow
   *  it to perform post-connection chores
   */
  public void postConnect();
  
  /**
   * Fetch the current view of memberships in th distributed system,
   * as an ordered list.
   * 
   * @return list of members
   */
  public NetView getView();
  /**
   * Returns an object that is used to sync access to the view.
   * While this lock is held the view can't change.
   * @since GemFire 5.7
   */
  public ReadWriteLock getViewLock();

  /**
   * Return a {@link InternalDistributedMember} representing the current system
   * 
   * @return an address corresponding to the current system
   */
  public InternalDistributedMember getLocalMember();

  /**
   * Sanity checking, esp. for elder processing.  Does the existing
   * member (still) exist in our view?
   * 
   * @param m the member
   * @return true if it still exists
   */
  public boolean memberExists(DistributedMember m);
  
  /**
   * Is this manager still connected?  If it has not been initialized, this
   * method will return true; otherwise it indicates whether the connection is
   * stil valid
   * @return true if the manager is still connected.
   */
  public boolean isConnected();
  
  
  /**
   * A test hook for healthiness tests
   */
  public boolean isBeingSick();
  
  /**
   * Instruct this manager to release resources
   */
  public void shutdown();
  
  /**
   * Forcibly shut down the manager and inform its listeners of the failure
   */
  public void uncleanShutdown(String reason, Exception e);
  
  /**
   * A shutdown message has been received from another member
   */
  public void shutdownMessageReceived(InternalDistributedMember id, String reason);
  
  /**
   * Stall the current thread until we are ready to
   * accept view events
   * 
   * @throws InterruptedException if the thread is interrupted while waiting
   * @see #startEventProcessing()
   */
  public void waitForEventProcessing() throws InterruptedException;
  
  /**
   * Commence delivering events to my listener.
   * 
   * @see #waitForEventProcessing()
   */
  public void startEventProcessing();
  
  
  /**
   * @param destinations list of members to send the message to.  A list of
   * length 1 with <em>null</em> as a single element broadcasts to all members
   * of the system.
   * @param content the message to send
   * @param stats the statistics object to update
   * @return list of members who did not receive the message.  If 
   * {@link DistributionMessage#ALL_RECIPIENTS} is given as thelist of recipients,
   * this return list is null (empty).  Otherwise, this list is all of those
   * recipients that did not receive the message because they departed the
   * distributed system.
   * @throws NotSerializableException
   *         If content cannot be serialized
   */
  public Set send(
      InternalDistributedMember[] destinations,
      DistributionMessage content,
      DMStats stats)
  throws NotSerializableException;
  
  /**
   * Indicates to the membership manager that the system is shutting down.
   * Typically speaking, this means that new connection attempts are to be
   * ignored and disconnect failures are to be (more) tolerated.
   *
   */
  public void setShutdown();
  

  /**
   * Determine whether GCS shutdown has commenced
   * @return true if it is shutting down
   */
  public boolean shutdownInProgress();

  /**
   * Returns a serializable map of communications state for
   * use in state stabilization.
   * @param member
   *    the member whose message state is to be captured
   * @param includeMulticast
   *    whether the state of the mcast messaging should be included
   * @return the current state of the communication channels between this
   *    process and the given distributed member
   * @since GemFire 5.1
   */
  public Map getMessageState(DistributedMember member, boolean includeMulticast);

  /**
   * Waits for the given communications to reach the associated
   * state
   * @param member
   *    The member whose messaging state we're waiting for
   * @param state
   *    The message states to wait for.  This should come from getMessageStates
   * @throws InterruptedException
   *    Thrown if the thread is interrupted
   * @since GemFire 5.1
   */
  public void waitForMessageState(DistributedMember member, Map state)
    throws InterruptedException;
  
  /**
   * Wait for the given member to not be in the membership view and for all direct-channel
   * receivers for this member to be closed.
   * @param mbr  the member
   * @return for testing purposes this returns true if the serial queue for the member was flushed
   * @throws InterruptedException if interrupted by another thread
   * @throws TimeoutException  if we wait too long for the member to go away
   */
  public boolean waitForDeparture(DistributedMember mbr) throws TimeoutException, InterruptedException;
  
  /**
   * Returns true if remoteId is an existing member, otherwise waits till
   * timeout. Returns false if remoteId is not confirmed to be a member.
   * 
   * @param remoteId
   * @return true if membership is confirmed, else timeout and false
   */
  public boolean waitForNewMember(InternalDistributedMember remoteId);

  /**
   * Release critical resources, avoiding any possibility
   * of deadlock
   * 
   * @see SystemFailure#emergencyClose()
   */
  public void emergencyClose();
  
  /**
   * Request the current membership coordinator to remove the given member
   */
  public boolean requestMemberRemoval(DistributedMember member, String reason);
  
  /**
   * like memberExists() this checks to see if the given ID is in the current
   * membership view.  If it is in the view though we try to connect to its
   * failure-detection port to see if it's still around.  If we can't then
   * suspect processing is initiated on the member with the given reason string.
   * @param mbr the member to verify
   * @param reason why the check is being done (must not be blank/null)
   * @return true if the member checks out
   */
  public boolean verifyMember(DistributedMember mbr, String reason);

  
  /**
   * Initiate SUSPECT processing for the given members.  This may be done if
   * the members have not been responsive.  If they fail SUSPECT processing,
   * they will be removed from membership.
   */
  public void suspectMembers(Set members, String reason);
  
  /**
   * Initiate SUSPECT processing for the given member.  This may be done if
   * the member has not been responsive.  If it fails SUSPECT processing,
   * it will be removed from membership.
   */
  public void suspectMember(DistributedMember member, String reason);
  
  /**
   * if the manager initiated shutdown, this will return the cause
   * of abnormal termination of membership management in this member
   * @return the exception causing shutdown
   */
  public Throwable getShutdownCause();
  
  /**
   * register a test hook for membership events
   * @see MembershipTestHook
   */
  public void registerTestHook(MembershipTestHook mth);
  
  /**
   * remove a test hook previously registered with the manager
   */
  public void unregisterTestHook(MembershipTestHook mth);

  /**
   * If this member is shunned, ensure that a warning is generated at least once.
   * 
   * @param mbr the member that may be shunned
   */
  public void warnShun(DistributedMember mbr);
  
  public boolean addSurpriseMember(DistributedMember mbr);
  
  /** if a StartupMessage is going to reject a new member, this should be used
   * to make sure we don't keep that member on as a "surprise member"
   * @param mbr the failed member
   * @param failureMessage the reason for the failure (e.g., license limitation)
   */
  public void startupMessageFailed(DistributedMember mbr, String failureMessage);

  /**
   * @return true if multicast is disabled, or if multicast is enabled and seems to be working
   */
  public boolean testMulticast();
  
  /**
   * Returns true if the member is a surprise member.
   *
   * @param m the member in question
   * @return true if the member is a surprise member
   */
  public boolean isSurpriseMember(DistributedMember m);
  
  /**
   * Returns true if the member is being shunned
   */
  public boolean isShunned(DistributedMember m);

  /**
   * Forces use of UDP for communications in the current thread.  UDP is
   * connectionless, so no tcp/ip connections will be created or used for
   * messaging until this setting is released with releaseUDPMessagingForCurrentThread.
   */
  public void forceUDPMessagingForCurrentThread();
  
  /**
   * Releases use of UDP for all communications in the current thread,
   * as established by forceUDPMessagingForCurrentThread.
   */
  public void releaseUDPMessagingForCurrentThread();
  
  
  /**
   * After a forced-disconnect this method should be used once before
   * attempting to use quorumCheckForAutoReconnect().
   * @return the quorum checker to be used in reconnecting the system
   */
  public QuorumChecker getQuorumChecker();
  
  
  /**
   * Frees resources used for quorum checks during auto-reconnect
   * polling.  Invoke this method when you're all done using
   * the quorum checker.
   * @param checker the QuorumChecker instance
   */
  public void releaseQuorumChecker(QuorumChecker checker);
  
}
